#! /usr/bin/env python
"""
::BOH
$Id: alert.py,v 1.38 2006/05/02 03:00:00

Copyright (c) 2006 George Bobeck. All rights reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
::EOH

Alert daemon
"""
import sys, os
from time import *
from datetime import *
import MySQLdb
import smtplib
from email.MIMEText import MIMEText

"""
Error Codes:
    0000        Alert Daemon Activated
    0404        Sensor Gone Inactive
    0666        Sensor Temperature Output Bad Data
    0H20        Sensor humidity output above 100, Bad Data
    1000        Temperature Related
    1001        Near Min Temperature
    1011        Near Max Temperature
    1002        At Min Temperture
    1012        At Max Temperture
    1003        Exceeded Min Temperature
    1013        Exceeded Max Temperature
    2001        Near Min Humidity
    2011        Near Max Humidity
    2002        At Min Humidity
    2012        At Max Humidity
    2003        Exceeded Min Humidity
    2013        Exceeded Max Humidity
    2H20        Humidity at 100%
Error Levels:
    X    Alert Daemon Activated
    0    Notice
    1    Low
    2    Medium
    3    Holy Crap, Batman!
For Temperature:
    Level 0:
        0666: temp < -30 C or temp > 85 C
    Level 1:
       min + 3 -> min + 1.5, max - 3 -> max - 1.5 
    Level 2:
        min -> min + 1.5 , max - 1.5 -> max
    Level 3:
        temp < min, temp > max
For humidity:
    X <=0 and x > 100 are ignored
    level 1:
        min + 3 - > min + 5, max -5 -> max - 3
    level 2:
        min -> min + 2, max -2 -> max
    level 3:
        below min, above max
"""

def conf(location):
    f = open(location,'r')
    a = f.readlines()
    fl = []
    var_list = []
    for e in a:
        if "#" not in e:
            e = e.strip('\n')
            e = e.strip('\r')
            if e == '':
                pass
            else:
                e = e.split("\t")
                try:
                    e.remove('')
                except:
                    pass
            var_list.extend(e)
    return var_list
global smtpserver
global resolution
global toaddr
global frmaddr
global verbose
#global smtp_verify
#smtp_verify=False
confs = conf(sys.argv[ 1 ])
verbose = bool(confs[confs.index("verbose")+1])
smtpserver = confs[confs.index("smtpserver")+1]
#
# change to conf file 
#
""" THIS IS LEFT FOR A FUTURE VERSION
smtp_verify = confs[confs.index("smtp_verify")+1]
if smtp_verify:
    global smtp_id
    global smtp_passwd
    smtp_id = confs[confs.index("smtp_id")+1]
    smtp_passwd = confs[confs.index("smtp_passwd")+1]
"""

toaddr = confs[confs.index("toaddr")+1]
frmaddr = confs[confs.index("frmaddr")+1]
resolution = int(confs[confs.index("resolution")+1])
dbhost = confs[confs.index("dbhost")+1]
dbport = int(confs[confs.index("dbport")+1])
dbuser = confs[confs.index("dbuser")+1]
dbpasswd = confs[confs.index("dbpasswd")+1]
dbase = confs[confs.index("dbase")+1]

if verbose:
    print "Alert Daemon Settings"
    print "Mail Server: ",smtpserver
    print "Resolution (in senconds): ",resolution
    print "Database Settings"
    print "Host: ",dbhost
    print "Port: ",dbport
    print "User: ",dbuser
    print "(Password Not Printed)"
    print "Database: ",dbase

def dbconnect():
    global ms
    ms = MySQLdb.connect(host = dbhost,port = dbport ,user = dbuser,passwd=dbpasswd,db=dbase)
    global cur
    cur = ms.cursor()
"""
def fudge(temp, pct):
    
    # Fudge the reading by a given percentage, positive or negative.
    
    if abs(pct) > 0 and abs(pct) < 1:
       if pct < 0:
         fudge_factor = (pct - 1) * temp
       else:
         fudge_factor = (pct + 1) * temp
    else:
       raise "bad pct %s" % pct
"""

def Temp_gen_alert(temp,t_min = 5, t_max = 35):
    """
    Default temp. values are: 
      t_min = 5 (41 F) 
      t_max = 35 (95 F)
    These are based off of the environmental 
    params of an IBM ThinkPad T43 laptop.
    
    This function returns a tuple.
    """
    alert = None
    level = None
    if (temp >= -30) and (temp <= 85):
        if (temp <= (t_min + 3)) and (temp > (t_min + 1.5)):
            level = "1"
            alert = "1001"
        elif (temp >= (t_max - 3)) and (temp < (t_max - 1.5)):
            level = "1"
            alert = "1011"
        elif (temp <= (t_min + 1.5)) and (temp >= t_min):
            level = "2"
            alert = "1002"
        elif (temp >= (t_max - 1.5)) and (temp <= t_max):
            level = "2"
            alert = "1012"
        elif (temp < t_min):
            level = "3"
            alert = "1003"
        elif (temp > t_max):
            level = "3"
            alert = "1013"
        else:
            level = None
            alert = None
    else:
        level = "0"
        alert = "0666"
    
    return level, alert

def Humid_gen_alert(humid, h_min = 10, h_max = 85):
    """
    Default humid. values are:
      h_min = 10
      h_max = 85
    These are based off of the environmental 
    params of a Cisco Catalyst WS-C3750G-24TS-E 10/100/1000 switch.
    
    This function returns a tuple.
    """
    alert = None 
    level = None
    
    if (humid > 0):
        if (humid > (h_min + 3)) and (humid <= (h_min + 5)):
            level = "1"
            alert = "2001"
        elif (humid <= (h_max - 3)) and (humid >= (h_max - 5)):
            level = "1"
            alert = "2011"
        elif (humid <= (h_min + 3)) and (humid >= h_min):
            level = "2"
            alert = "2002"
        elif (humid >= (h_max - 3)) and (humid <= h_max):
            level = "2"
            alert = "2012"
        elif (humid < h_min):
            level = "3"
            alert = "2003"
        elif (humid > h_max) and (humid < 100):
            level = "3"
            alert = "2013"
        elif (humid == 100):
            level = "3"
            alert = "2H20"
        elif (humid > 100):
            level = "0"
            alert = "0H20"
        else:
            # If Humid > 0 and not alertable
            level = None
            alert = None
    else:
        # Humid <= 0
        level = None
        alert = None
    
    return level, alert

def Alert_msg_gen(sid,location,alert):
    if alert == "0404":
        alert_msg = "Sensor %s, located at %s, has gone inactive" %(sid,location)
    elif alert == "0666":
        alert_msg = "Sensor %s has output a really invalid temperature value" %(sid)
    elif alert == "0H20":
        alert_msg = "Sensor %s has output a really invalid humidity value" %(sid)
    elif alert == "1001":
        alert_msg = "Sensor %s is reporting that the location '%s' is nearing the minimum temperature limit." %(sid,location)
    elif alert == "1011":
        alert_msg = "Sensor %s is reporting that the location '%s' is nearing the maximum temperature limit." %(sid,location)
    elif alert == "1002":
        alert_msg = "Sensor %s is reporting that the location '%s' is at the minimum temperature." %(sid,location)
    elif alert == "1012":
        alert_msg = "Sensor %s is reporting that the location '%s' is at the maximum temperature." %(sid,location)
    elif alert == "1003":
        alert_msg = "Sensor %s is reporting that the location '%s' below the minimum temperature!" %(sid,location)
    elif alert == "1013":
        alert_msg = "Sensor %s is reporting that the location '%s' is above the maximum temperature!" %(sid,location)
    elif alert == "2001":
        alert_msg = "Sensor %s is reporting that the location '%s' is nearing the minimum humidity limit." %(sid,location)
    elif alert == "2011":
        alert_msg = "Sensor %s is reporting that the location '%s' is nearing the maximum humidity limit." %(sid,location)
    elif alert == "2002":
        alert_msg = "Sensor %s is reporting that the location '%s' is at the minimum humidity limit." %(sid,location)
    elif alert == "2012":
        alert_msg = "Sensor %s is reporting that the location '%s' is at the maximum humidity limit." %(sid,location)
    elif alert == "2003":
        alert_msg = "Sensor %s is reporting that the location '%s' is below the minimum humidity limit!" %(sid,location)
    elif alert == "2013":
        alert_msg = "Sensor %s is reporting that the location '%s' is above the maximum humidity limit!" %(sid,location)
    else:
        alert_msg = "Sensor %s is reporting that the location '%s' is either at 100% humidity or under water!" %(sid,location)
    return alert_msg

def st2dt(x):
    d = datetime(int(x[0:4]),int(x[5:7]),int(x[8:10]),int(x[11:13]),int(x[14:16]),int(x[17:19]))
    return d

def email_alert(toaddrs,fromaddr,sid,location,alert):  
    msg = MIMEText(Alert_msg_gen(sid,location,alert))
    msg['Subject'] = "OWTF Alert: %s" %(sid)
    msg['From'] = fromaddr
    msg['To'] =toaddrs
    
    global smtpserver
    #global smtp_verify
    global verbose
 
    
    server = smtplib.SMTP(smtpserver)
    #if smtp_verify:
        #global smtp_id
        #global smtp_passwd
        #server.login(smtp_id, smtp_passwd)
    if verbose:
        server.set_debuglevel(1)
    server.sendmail(fromaddr, toaddrs, msg.as_string())
    server.quit()

def main():
    global resolution
    try:
        # try to connect to db
        dbconnect()
        global cur
        c_time = datetime.today()
        c_time = c_time.strftime("%Y-%m-%d %H:%M:%S")
        cur.execute("""insert ignore into alert values (" DAEMON STARTED ",%s,"X","0000")""",(c_time))
    except:
        print "Unable to Connect to Database"
        sys.exit(1)
    while 1:
        c_time = datetime.today()
        m_time = timedelta(seconds = (resolution + 10))
        p_time = c_time - m_time #past time is current time minus m_time
        del_time = c_time - timedelta(days = 32) #deleteion time
        inactive_time = c_time - timedelta(days = 7) #deleteion time
        c_time = c_time.strftime("%Y-%m-%d %H:%M:%S")
        p_time = p_time.strftime("%Y-%m-%d %H:%M:%S")
        
        
        del_time = del_time.strftime("%Y-%m-%d %H:%M:%S")
        inactive_time = inactive_time.strftime("%Y-%m-%d %H:%M:%S")

        try:
            if verbose:
                print "executing"
            # delete very old events
            cur.execute("""delete from sensor_data where cron < %s""",(del_time))
            # set sensors which haven't spoken for 7 days to inactive
            cur.execute("""update sensors set active = 'N' where cron < %s""",(inactive_time))
            
            cur.execute("""select * from sensor_data where cron > %s""",(p_time))
            x = cur.fetchall()
            list(x)
            cur.execute("""select * from sensors where active = 'N' """)
            z = cur.fetchall()
            list(z)                        
            h_list = []
            t_list = []
            
            global toaddr
            global frmaddr
            for i in z:
                if verbose:
                    print "inactive"
                
                # FOR A FUTURE VERSION
                #c2_time = datetime.today() - timedelta(days = 1)
                #c2_time = c2_time.strftime("%Y-%m-%d %H:%M:%S")
                #cur.execute("""select * from alert where sid=%s and event = '0404' and cron > %s""",(i[1],c2_time))
                #inactives = cur.fetchall()
                #if i[1] == inactives[0][0] and c_time > c2_time:
                #    print "Yes, its in there!"
                #else:
                #    pass
                
                cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[1],c_time,"0","0404"))
                cur.execute("""select location from sensors where sid = %s""",(i[1]))
                y = cur.fetchall()
                email_alert(toaddr,frmaddr,i[1],y[0][0],"0404")
                if verbose:
                    print "email, sent"                                
            for i in x:
                if verbose:
                    print "t"
                if cur.execute("""select temp_min,temp_max,humid_min,humid_max,v1_min,v1_max,v2_min,v2_max from alertables where sid = %s""",(i[0])):
                    funstuff = cur.fetchone()
                    if verbose:
                        print i[0],funstuff
                    t = Temp_gen_alert(i[2],funstuff[0],funstuff[1])
                    h = Humid_gen_alert(i[3],funstuff[2],funstuff[3])
                else:
                    t = Temp_gen_alert(i[2])
                    h = Humid_gen_alert(i[3])
                if t[0] and t[1]:
                    t2 = (i,t)
                    t_list.append(t2)
                if h[0] and h[1]:
                    h2 = (i,h)
                    h_list.append(h2)
            
            while t_list:
                i = t_list.pop()
                                
                cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[0][0],i[0][1],i[1][0],i[1][1]))
                cur.execute("""select location from sensors where sid = %s""",(i[0][0]))
                y = cur.fetchall()
                
                try:
                    #this is a problem                                       
                    email_alert(toaddr,frmaddr,i[0][0],y[0][0],i[1][1])
                    if verbose:
                        print "email sent"
                except:    
                    if verbose:
                        print "email alert function failed"
                    else:
                        pass
                    
            while h_list:
                i = h_list.pop()
                cur.execute("""insert ignore into alert values(%s,%s,%s,%s)""",(i[0][0],i[0][1],i[1][0],i[1][1]))
                cur.execute("""select location from sensors where sid = %s""",(i[0][0]))
                y = cur.fetchall()
                if st2dt(i[0][1])> dt_state:
                    dt_state = st2dt(i[0][1])
                try:
                    email_alert(toaddr,frmaddr,i[0][0],y[0][0],i[1][1])
                except:
                    if verbose:
                        print "email alert function failed"
                    else:
                        pass
            del h_list
            del t_list
        except:
            pass
        sleep( resolution )
        
    # Close db
    ms.close()
    sys.exit( 1 )

if __name__ == "__main__":
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit first parent
            sys.exit(0) 
    except OSError, e: 
        print >>sys.stderr, "ERROR: fork #1 failed: %d (%s)" % (e.errno, e.strerror) 
        sys.exit(1)
    # decouple from parent environment
    os.chdir("/") 
    os.setsid() 
    os.umask(0) 
    # do second fork
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit from second parent, print eventual PID before
            if verbose:
                print "Monitor PID %d" % pid 
            sys.exit(0) 
    except OSError, e: 
        print >>sys.stderr, "ERROR: fork #2 failed: %d (%s)" % (e.errno, e.strerror) 
        sys.exit(1) 
    main()

